﻿using Core = Cofe.Core;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Cofe.Core.Interfaces;
using System.ComponentModel.Composition;
using Cofe.Core.Filter;
using Cofe.Core.Service;
using Cofe.Core.Entry;


namespace Cofe.Core.Property
{
    public class ListActionProvider : PropertyProviderBase<IPropertyHost>, IListActionProvider
    {
        #region Factory
        [Export(typeof(ICofeService))]
        [ServicePriority(ServicePriorityAttribute.DefaultPriority_COFE)]
        public class Factory : ISecondaryPropertyProviderFactory
        {
            public IEnumerable<IPropertyProvider> GetSecoondaryPropertyProviders(IPropertyHost propertyHost)
            {
                bool isDirectory = propertyHost.Behaviors.IsPropertySupported(CofeDirectoryProperties.ListCore) ||
                    propertyHost.Behaviors.IsPropertySupported(CofeDirectoryProperties.ListCoreAsync);

                if (!(propertyHost.SecondaryPropertyProviders.Any((p) => p is IListActionProvider)) && isDirectory)
                    yield return new ListActionProvider(propertyHost, ServiceLocater.FindService<IEntryConstructor>());
            }


            private IServiceLocater _serviceLocater = null;
            public IServiceLocater ServiceLocater
            {
                get { return _serviceLocater ?? CofeServices.ServiceLocater; }
                set { _serviceLocater = value; }
            }

        }
        #endregion

        #region Constructor

        public ListActionProvider(IPropertyHost propertyHost, IEntryConstructor entryConstructor)
            : base(propertyHost,
            propertyHost.SupportedDefinitions.Intersect(
             new IPropertyDefinitions[] {                  
                 Core.PropertyDefinitions.CofeDirDefinitions 
             }, PropertyDefinitionComparer.CompareCategory).ToArray())
        {
            EntryConstructor = entryConstructor;

            registerActionAsync<IEnumerable<ICofeSystemInfo>>(CofeDirectoryProperties.ListAsync,
                (pm) => Task.Run<IEnumerable<ICofeSystemInfo>>(() => listAsync(pm)));
            registerAction<IEnumerable<ICofeSystemInfo>>(CofeDirectoryProperties.List,
                (pm) => list(pm));

            registerProperties(CofeDirectoryProperties.HasSubFolders, () =>
                {
                    return list(new ParameterDic()).Any(p => p.Properties.SupportedDefinitions.Contains(
                        Cofe.Core.PropertyDefinitions.CofeDirDefinitions, PropertyDefinitionComparer.CompareCategory));
                });

            registerProperties(CofeDirectoryProperties.HasFiles, () =>
            {
                return list(new ParameterDic()).Any(p => p.Properties.SupportedDefinitions.Contains(
                    Cofe.Core.PropertyDefinitions.CofeFileDefinitions, PropertyDefinitionComparer.CompareCategory));
            });
        }

        #endregion

        #region Methods

        private IPropertyHost getPropertyHost(IPropertyProvider propProvider)
        {
            return EntryConstructor.ConstructPropertyHost(propProvider);
        }

        protected virtual void loadParameters(ParameterDic parameters, out IEntryFilter[] entryFilters, out IOptionFilter[] optionFilters)
        {
            List<IFilter> allFilters = new List<IFilter>();
            if (parameters.ContainsKey("EntryFilters") && parameters["EntryFilters"] is IEntryFilter[])
                allFilters.AddRange(parameters["EntryFilters"] as IEntryFilter[]);
            if (parameters.ContainsKey("OptionFilters") && parameters["OptionFilters"] is IOptionFilter[])
                allFilters.AddRange(parameters["OptionFilters"] as IOptionFilter[]);
            foreach (var param in parameters)
                if (param.Value is string)
                {
                    var f = CofeServices.EntryFilterParser.CreateFilter(param.Key, (string)param.Value);
                    if (f != null)
                        allFilters.Add(f);
                }
            entryFilters = allFilters.Where(f => f is IEntryFilter).Select(f => f as IEntryFilter).ToArray();
            optionFilters = allFilters.Where(f => f is IOptionFilter).Select(f => f as IOptionFilter).ToArray();
        }

        private static bool isListSupported(IPropertyHost ph)
        {
            return ph.SupportedDefinitions.IsPropertySupported(CofeDirectoryProperties.List) ||
            ph.SupportedDefinitions.IsPropertySupported(CofeDirectoryProperties.ListAsync);
        }

        private async Task<IEnumerable<ICofeSystemInfo>> listAsync(IPropertyHost parent,
           IEntryFilter[] entryFilters, IOptionFilter[] optionFilters)
        {
            try
            {
                var entryList =
                    from subEntryPP in
                        await parent.Behaviors.InvokeAsync<IEnumerable<IPropertyProvider>>(CofeDirectoryProperties.ListCoreAsync,
                        ParameterDic.Empty)
                    select getPropertyHost(subEntryPP);

                var currentEntries = entryList
                    .Where(eph => entryFilters.MatchAll(eph, MatchType.MatchThisEntry, optionFilters))
                    .Select(eph => CofeServices.EntryConstructor.ConstructEntry(eph));

                if (optionFilters.GetSearchOption() == SearchOption.TopDirectoryOnly)
                    return currentEntries;

                else
                {
                    var subEntriesPP = entryList
                       .Where(eph =>
                           isListSupported(eph) && entryFilters.MatchAll(eph, MatchType.MatchSubEntry, optionFilters))
                        .SelectMany(eph => Task.Run<IEnumerable<ICofeSystemInfo>>(async () =>
                            await listAsync(eph, entryFilters, optionFilters)).Result);

                    return currentEntries.Union(subEntriesPP);
                }
            }
            catch (System.IO.FileNotFoundException)
            {
                string parseName = parent.Behaviors.GetProperty<string>(CofeProperties.FullParseName);
                CofeServices.Logger.Log(String.Format("LAP:Unable to list {0} because it's removed", parseName), Category.Exception, Priority.Medium);
                return new List<ICofeSystemInfo>();
            }


        }

        protected virtual async Task<IEnumerable<ICofeSystemInfo>> listAsync(ParameterDic parameters)
        {
            parameters.IsHandled = true;
            IEntryFilter[] entryFilters; IOptionFilter[] optionFilters;
            loadParameters(parameters, out entryFilters, out  optionFilters);
            return await listAsync(Entity, entryFilters, optionFilters);
        }


        protected virtual IEnumerable<ICofeSystemInfo> list(IPropertyHost parent,
            IEntryFilter[] entryFilters, IOptionFilter[] optionFilters)
        {
            var entryPPList = parent.Behaviors.Invoke<IEnumerable<IPropertyProvider>>(CofeDirectoryProperties.ListCore,
                    ParameterDic.Empty);
            var entryList =
                from subEntryPP in entryPPList
                select getPropertyHost(subEntryPP);

            foreach (var ph in entryList)
            {
                if (entryFilters.MatchAll(ph, MatchType.MatchThisEntry, optionFilters))
                    yield return CofeServices.EntryConstructor.ConstructEntry(ph);
                if (ph.SupportedDefinitions.IsPropertySupported(CofeDirectoryProperties.ListCore) ||
                    ph.SupportedDefinitions.IsPropertySupported(CofeDirectoryProperties.ListCoreAsync))
                    if (entryFilters.MatchAll(ph, MatchType.MatchSubEntry, optionFilters))
                        foreach (var subEntry in list(ph, entryFilters, optionFilters))
                            yield return subEntry;
            }
        }

        protected virtual IEnumerable<ICofeSystemInfo> list(ParameterDic parameters)
        {
            parameters.IsHandled = true;
            IPropertyHost eph = Entity;
            IEntryFilter[] entryFilters; IOptionFilter[] optionFilters;
            loadParameters(parameters, out entryFilters, out  optionFilters);
            return list(eph, entryFilters, optionFilters);
        }


        #endregion

        #region Data


        #endregion

        #region Public Properties

        /// <summary>
        /// For Debug only.
        /// </summary>
        public ISecondaryPropertyProviderFactory[] SecondaryPropertyProviderFactories { get; set; }

        /// <summary>
        /// For Debug only.
        /// </summary>
        public IPathParser[] PathParsers { get; set; }

        public IEntryConstructor EntryConstructor { get; internal set; }

        #endregion


    }
}
